#include "input.h"

#include "../Epsilon/debug.h"
#include "../Epsilon/window.h"
#include "../Epsilon/Epsilon3D/rendercontext.h"

#include "../Epsilon/EpsilonInput/keyboard.h"
#include "../Epsilon/EpsilonInput/mouse.h"

#define eKeyboard Epsilon::Keyboard
#define eMouse    Epsilon::Mouse

#include "window.h"

#include "camera.h"
#include "cli_parse/states.h"

eInput::eInput (eWindow* win)
{
  window_      = win;

#ifdef HAS_D3D
  m_pCursorTexture    = NULL;

  m_pDIObject         = NULL;
  m_pDIKeyboardDevice = NULL;
  m_pDIMouseDevice    = NULL;
#endif

  Resize (win->getWidth (), win->getHeight ());

  //if(!InitDirectInput()) g_App.SetD3DStatus(false); //init DirectInput
  InitMouse       ();

  Epsilon::Keyboard* keyboard = win->getKeyboard ();
  Epsilon::Mouse*    mouse    = win->getMouse    ();

  if (keyboard != NULL)
    keyboard->InstallListener (this);

  if (mouse != NULL)
    mouse->InstallListener (this);

  dirty_state_ = false;

  ShowCursor (true);
} //eInput

eInput::~eInput (void)
{
} //~eInput

void
eInput::Resize (DWORD dwWidth, DWORD dwHeight)
{
  hot_spot_x_ = 0;
  hot_spot_y_ = 0;

  /// TODO: ...
  //  cursor_pos_x_ = (float)dwWidth  / 2;
  //  cursor_pos_y_ = (float)dwHeight / 2;

  screen_width_  = dwWidth;
  screen_height_ = dwHeight;
}

#if 0
bool
eInput::CursorOnScreen (void)
{
#ifdef WIN32
  POINT pos;
  GetCursorPos (&pos);

  ScreenToClient (eHWND (window_->getNativeWindowHandle ()), &pos);

  m_iDesktopCursorX = pos.x;
  m_iDesktopCursorY = pos.y;
#endif

  m_bCursorOnScreen = true;

  /*
  if (m_iDesktopCursorX > m_dwScreenWidth || m_iDesktopCursorX < 0) {
    m_bCursorOnScreen = false;
  }

  if (m_iDesktopCursorY > m_dwScreenHeight || m_iDesktopCursorY < 0) {
    m_bCursorOnScreen = false;
  }
  */

  return m_bCursorOnScreen;
}
#endif

void print_camera_pos (void);

bool
eInput::Update (void)
{
  Epsilon::Mouse* mouse = states->window->win->getMouse ();

  if (! mouse)
    return false;

  /* If no mouse events happened, then do not do anything! */
  if (! dirty_state_)
    return false;

  dirty_state_  = false;
  cursor_pos_x_ = mouse->GetX ();
  cursor_pos_y_ = mouse->GetY ();

  mouse_state_ = *mouse;

  static Epsilon::Mouse state = mouse_state_;
    
//  if (m_bCursorOnScreen) {
    /* Run through all installed listeners, notifying each of the
         event ... */
    std::vector <eInput_iListener *>::iterator listener =
      listeners_.begin ();
    std::vector <eInput_iListener *>::iterator end =
      listeners_.end ();

    while (eSTL::IterValid (listener, end)) {
      (*listener)->OnMouse (mouse_state_, state);
      ++listener;
    }

    state = mouse_state_;

    mouse->GetDeltaX (true);
    mouse->GetDeltaY (true);
//  }

  return true;
} //Update


//#define HW_CURSOR

bool
eInput::InitMouse (void)
{
/// TODO: Port
#if 0
  m_MouseStateLast.lX = 0;
  m_MouseStateLast.lY = 0;

#ifdef HW_CURSOR
  ::SetCursor                  (NULL);
  e3dDirect3D::dev->ShowCursor (TRUE);
#else
  if (e3dDirect3D::IsDevicePresent ())
    SetCursor (_T ("arrow.bmp"), 0, 0, 0xffffffff);
  else
    ShowCursor (TRUE);
#endif

  SetAttributes (false, 1.0f);

  POINT pos;
  GetCursorPos (&pos);

  ScreenToClient (eHWND (window_->getNativeWindowHandle ()), &pos);

  m_fCursorX = (float)pos.x;
  m_fCursorY = (float)pos.y;
#endif

  SetAttributes (false, 0.01f);

  return true;
} //InitMouse

enum {
  DIK_RSHIFT,
  DIK_LSHIFT,
  DIK_RCONTROL,
  DIK_LCONTROL,
  DIK_W,
  DIK_UP,
  DIK_S,
  DIK_DOWN,
  DIK_A,
  DIK_LEFT,
  DIK_D,
  DIK_RIGHT,

  DIK_GRAVE = 15,
  DIK_PERIOD,
  DIK_COMMA,
  DIK_BACKSLASH,
  DIK_ESCAPE,
  DIK_SLASH,
  DIK_SPACEBAR
};

bool
eInput::KeyPressed (int Key)
{
#define eKeyboard Epsilon::Keyboard
eKeyboard* keyboard = states->window->win->getKeyboard ();

  if (! keyboard)
    return false;

  switch (Key)
  {
    case DIK_RSHIFT:
    case DIK_LSHIFT:
      return keyboard->GetKey (eKeyboard::ModifierKey (eKeyboard::ModifierKeys::Shift));
    case DIK_RCONTROL:
    case DIK_LCONTROL:
      return keyboard->GetKey (eKeyboard::ModifierKey (eKeyboard::ModifierKeys::Control));
    case DIK_W:
      return keyboard->GetKey (eKeyboard::TextKey (eKeyboard::TextKeys::W));
    case DIK_A:
      return keyboard->GetKey (eKeyboard::TextKey (eKeyboard::TextKeys::A));
    case DIK_S:
      return keyboard->GetKey (eKeyboard::TextKey (eKeyboard::TextKeys::S));
    case DIK_D:
      return keyboard->GetKey (eKeyboard::TextKey (eKeyboard::TextKeys::D));
    case DIK_UP:
      return keyboard->GetKey (eKeyboard::MiscKey (eKeyboard::MiscKeys::Up));
    case DIK_DOWN:
      return keyboard->GetKey (eKeyboard::MiscKey (eKeyboard::MiscKeys::Down));
    case DIK_LEFT:
      return keyboard->GetKey (eKeyboard::MiscKey (eKeyboard::MiscKeys::Left));
    case DIK_RIGHT:
      return keyboard->GetKey (eKeyboard::MiscKey (eKeyboard::MiscKeys::Right));
    case DIK_PERIOD:
       return keyboard->GetKey (eKeyboard::TextKey (eKeyboard::TextKeys::RightBracket));
    case DIK_COMMA:
       return keyboard->GetKey (eKeyboard::TextKey (eKeyboard::TextKeys::LeftBracket));

    case DIK_BACKSLASH:
       return keyboard->GetKey (eKeyboard::TextKey (eKeyboard::TextKeys::BackSlash));

    case DIK_SLASH:
    	return keyboard->GetKey (eKeyboard::TextKey (eKeyboard::TextKeys::Slash));


    case DIK_ESCAPE:
       return keyboard->GetKey (eKeyboard::MiscKey (eKeyboard::MiscKeys::Escape));

    case DIK_GRAVE:
      return keyboard->GetKey (eKeyboard::TextKey (eKeyboard::TextKeys::WHATTHEHELL));

    
    case DIK_SPACEBAR:
      return keyboard->GetKey (eKeyboard::MiscKey (eKeyboard::MiscKeys::Space));

    // TEMP HACK - FN Key
    case 68992:
      return keyboard->GetKey (eKeyboard::ModifierKey (eKeyboard::ModifierKeys::Fn));

  };
return false;
} //KeyPressed


void
eInput::SetAttributes (bool invert, float sensitivity)
{
  invert_      = invert;
  sensitivity_ = sensitivity;
} //SetAttributes

void
eInput::SetCursorPos (int new_x, int new_y)
{
  window_->SetCursorPos (new_x, new_y);

  cursor_pos_x_ = new_x;
  cursor_pos_y_ = new_y;
} // SetCursorPos

int
eInput::GetDeltaX (void)
{
  int delta = mouse_state_.GetDeltaX (true);

  return delta;// * sensitivity_;
} // GetDeltaX

int
eInput::GetDeltaY (void)
{
  int delta = mouse_state_.GetDeltaY (true);

  return delta;// * sensitivity_;
} // GetDeltaY

/* Z-Axis --> Mousewheel */
int
eInput::GetDeltaZ (void)
{
  /// TODO: Scroll Wheel
  return 0;//0 * sensitivity_;
} // GetDeltaZ

int
eInput::GetCursorX (void)
{
  return cursor_pos_x_;
}

int
eInput::GetCursorY (void)
{
  return cursor_pos_y_;
}

/**
 * Returns true if the passed button is pressed for the given state...
 *
 *   If no state is given, use the internally stored state.
 *
**/
bool
eInput::IsMouseButtonDown (Mouse::Buttons Button, Epsilon::Mouse* state)
{
  if (! state)
    state = &mouse_state_;

  switch (Button)
  {
    case Mouse::Left:
      return state->IsButtonDown (eMouse::Buttons::Left);
      break;
    case Mouse::Middle:
      return state->IsButtonDown (eMouse::Buttons::Middle);
      break;
    case Mouse::Right:
      return state->IsButtonDown (eMouse::Buttons::Right);
      break;
  }

  return false;
} //MouseButtonDown


#include "../Epsilon/Epsilon3D/rendercontext.h"
#include "../Epsilon/Epsilon3D/OpenGL.h"

void
eInput::DrawCursor (void)
{
  /* Do nothing if the cursor is supposed to be hidden... */
  if (! show_cursor_)
    return;

  // If the HW cursor is visible, then don't draw the SW cursor!!! */
  if (window_->GetCursorState () == true)
    return;

#if 0 /// XXX: PORT ME
#ifdef HW_CURSOR
  e3dDirect3D::GetDev ()->SetCursorPosition (m_fCursorX, m_fCursorY, D3DCURSOR_IMMEDIATE_UPDATE);
  return;
#endif
#endif

//  if ((! m_bCursorOnScreen) || MouseButtonDown (eInput::Mouse::Middle)) {
//    return;
//  }

  extern bool ApplyEffectTexture (unsigned int idx);
  ApplyEffectTexture (2047);

#if 0

#ifdef HAS_D3D
  if (dev) {
    dev->SetFVF               ( D3DFVF_XYZRHW  |
                                D3DFVF_DIFFUSE |
                                D3DFVF_TEX1 );

    dev->SetRenderState       (D3DRS_ALPHABLENDENABLE, true);
    dev->SetRenderState       (D3DRS_SRCBLEND,         D3DBLEND_SRCALPHA);
    dev->SetRenderState       (D3DRS_DESTBLEND,        D3DBLEND_INVSRCALPHA);

    dev->SetTextureStageState (0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);

    //dev->SetTexture           (0, m_pCursorTexture);
    dev->DrawPrimitiveUP      (D3DPT_TRIANGLESTRIP, 2, aCursorVertices, sizeof (CursorVertex));

    dev->SetRenderState       (D3DRS_ALPHABLENDENABLE, false);
  } else {
#endif
    glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
    glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);

#if 0
    GLdouble mv   [16]; glGetDoublev  (GL_MODELVIEW_MATRIX,  mv);
#else
    GLdouble mv   [16];
    float    mv_  [16];
    memcpy (mv_, cli_parse::states->cl_player.Camera ()->GetModelview (), sizeof (float) * 16);

    for (int i = 0; i < 16; i++) {
      mv [i] = (double)mv_ [i];
    }
#endif

    GLdouble proj [16]; glGetDoublev  (GL_PROJECTION_MATRIX, proj);
    GLint    vp   [4];  glGetIntegerv (GL_VIEWPORT,          vp);

    double x,y,z;

    glPushAttrib (GL_ENABLE_BIT | GL_DEPTH_BUFFER_BIT);

    glDisable   (GL_DEPTH_TEST);
    glDepthMask (GL_FALSE);

    glDisable   (GL_LIGHTING);
    glDisable   (GL_COLOR_MATERIAL);

    glDisable   (GL_TEXTURE_2D);

    glEnable    (GL_ALPHA_TEST);
    glAlphaFunc (GL_NOTEQUAL, 0.0f);

    glDisable   (GL_BLEND);
    glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    glDisable   (GL_CULL_FACE);

    gluUnProject (         m_fCursorX - m_iHotSpotX,
                  vp [3] - m_fCursorY - m_iHotSpotY - 32.0f,
                  0.999999f,
                  mv,
                  proj,
                  vp,
                  &x,
                  &y,
                  &z);

    aCursorVertices [0].pos [0] = (float)x;
    aCursorVertices [0].pos [1] = (float)y;
    aCursorVertices [0].pos [2] = (float)z;
    aCursorVertices [0].st  [1] = 0.0f;

    gluUnProject (         m_fCursorX - m_iHotSpotX,
                  vp [3] - m_fCursorY - m_iHotSpotY,
                  0.99999f,
                  mv,
                  proj,
                  vp,
                  &x,
                  &y,
                  &z);

    aCursorVertices [1].pos [0] = (float)x;
    aCursorVertices [1].pos [1] = (float)y;
    aCursorVertices [1].pos [2] = (float)z;
    aCursorVertices [1].st  [1] = 1.0f;

    gluUnProject (         m_fCursorX - m_iHotSpotX + 32.0f,
                  vp [3] - m_fCursorY - m_iHotSpotY - 32.0f,
                  0.999999f,
                  mv,
                  proj,
                  vp,
                  &x,
                  &y,
                  &z);

    aCursorVertices [2].pos [0] = (float)x;
    aCursorVertices [2].pos [1] = (float)y;
    aCursorVertices [2].pos [2] = (float)z;
    aCursorVertices [2].st  [1] = 0.0f;

    gluUnProject (         m_fCursorX - m_iHotSpotX + 32.0f,
                  vp [3] - m_fCursorY - m_iHotSpotY,
                  0.999999f,
                  mv,
                  proj,
                  vp,
                  &x,
                  &y,
                  &z);

    aCursorVertices [3].pos [0] = (float)x;
    aCursorVertices [3].pos [1] = (float)y;
    aCursorVertices [3].pos [2] = (float)z;
    aCursorVertices [3].st  [1] = 1.0f;

    glEnableClientState (GL_VERTEX_ARRAY);
    glEnableClientState (GL_TEXTURE_COORD_ARRAY);
    glEnableClientState (GL_COLOR_ARRAY);

    glVertexPointer   (4, GL_FLOAT,         sizeof (CursorVertex), &aCursorVertices [0].pos);
    glColorPointer    (4, GL_UNSIGNED_BYTE, sizeof (CursorVertex), &aCursorVertices [0].color);
    glTexCoordPointer (2, GL_FLOAT,         sizeof (CursorVertex), &aCursorVertices [0].st);

    glDrawArrays (GL_TRIANGLE_STRIP, 0, 4);

    glPopAttrib ();
#else

  struct CursorVertex
  {
    int           pos   [4];
    unsigned char color [4];
    float         st    [2];
  };

  CursorVertex aCursorVertices [4];

  CursorVertex* pVert = aCursorVertices;

  float color = 1.0f;

  for (int i = 0; i < 4; i++) {
            aCursorVertices [i].pos [2] = 0;
            aCursorVertices [i].pos [3] = 1; // w = 1.0f
    (DWORD&)aCursorVertices [i].color   = D3DCOLOR_COLORVALUE (1.0f, 1.0f, 1.0f, color);
  }

  pVert->pos [0] = cursor_pos_x_;//0.0f;
  pVert->pos [1] = screen_height_ - cursor_pos_y_ - 32;//0.0f;
  pVert->st  [0] = 0.0f;
  pVert->st  [1] = 0.0f;

  pVert++;

  pVert->pos [0] = cursor_pos_x_;
  pVert->pos [1] = screen_height_ - cursor_pos_y_;
  pVert->st  [0] = 0.0f;
  pVert->st  [1] = 1.0f;

  pVert++;

  pVert->pos [0] = cursor_pos_x_ + 32;
  pVert->pos [1] = screen_height_ - cursor_pos_y_ - 32;
  pVert->st  [0] = 1.0f;
  pVert->st  [1] = 0.0f;

  pVert++;

  pVert->pos [0] = cursor_pos_x_ + 32;
  pVert->pos [1] = screen_height_ - cursor_pos_y_;
  pVert->st  [0] = 1.0f;
  pVert->st  [1] = 1.0f;

  glPushAttrib       (GL_ALL_ATTRIB_BITS);
  glPushClientAttrib (GL_CLIENT_ALL_ATTRIB_BITS);

  glMatrixMode   (GL_MODELVIEW);
  glPushMatrix   ();
  glLoadIdentity ();

  glMatrixMode   (GL_PROJECTION);
  glPushMatrix   ();
  glLoadIdentity ();

  glOrtho (0, screen_width_, 0, screen_height_, -1.0f, 1.0f);

  glDisable   (GL_DEPTH_TEST);
  glFrontFace (GL_CW);

//  cli_parse::states->render_context->SetCullFaces (e3dRenderStates::False);
//  cli_parse::states->render_context->SetDepthTest (e3dRenderStates::False);

  glDisable (GL_COLOR_MATERIAL);
  glEnable  (GL_TEXTURE_2D);

  glDisableClientState (GL_COLOR_ARRAY);

  glEnable    (GL_ALPHA_TEST);
  glAlphaFunc (GL_GEQUAL, 0.01f);

  glDisable (GL_BLEND);
  //glBlendFunc (GL_ONE, GL_ONE);

  glBegin (GL_TRIANGLE_STRIP);

  glColor4f (1.0f, 1.0f, 1.0f, 1.0f);

  glTexCoord2f (aCursorVertices [0].st [0],
                aCursorVertices [0].st [1]);

  glVertex2i (aCursorVertices [0].pos [0],
              aCursorVertices [0].pos [1]);

  glTexCoord2f (aCursorVertices [1].st [0],
                aCursorVertices [1].st [1]);

  glVertex2i (aCursorVertices [1].pos [0],
              aCursorVertices [1].pos [1]);

  glTexCoord2f (aCursorVertices [2].st [0],
                aCursorVertices [2].st [1]);

  glVertex2i (aCursorVertices [2].pos [0],
              aCursorVertices [2].pos [1]);

  glTexCoord2f (aCursorVertices [3].st [0],
                aCursorVertices [3].st [1]);

  glVertex2i (aCursorVertices [3].pos [0],
              aCursorVertices [3].pos [1]);

  glEnd ();

//  glDisable (GL_BLEND);

  glMatrixMode (GL_PROJECTION);
  glPopMatrix  ();

  glMatrixMode (GL_MODELVIEW);
  glPopMatrix  ();

  glPopClientAttrib ();
  glPopAttrib       ();

  //cli_parse::states->render_context->SetCullFaces (e3dRenderStates::True);
  //cli_parse::states->render_context->SetDepthTest (e3dRenderStates::True);

#endif

#ifdef HAS_D3D
  }
#endif
} //DrawCursor


bool
eInput::InstallListener (eInput_iListener* pListener)
{
  if (pListener == NULL)
    return false;

  /* Although rare, we should check for and avoid
       double-insertion of a listener... */
  std::vector <eInput_iListener *>::const_iterator listener =
    listeners_.begin ();
  std::vector <eInput_iListener *>::const_iterator end =
    listeners_.end ();

  while (eSTL::IterValid (listener, end)) {
//      assert ((*listener) != pListener);
    if ((*listener) == pListener)
      return false;

    ++listener;
  }

  // TEMP HACK: Install listeners LIFO, better support for GUI console...
  listeners_.insert (listeners_.begin (), pListener);
//  listeners_.push_back (pListener);

  return true;
}

bool
eInput::UninstallListener (eInput_iListener* pListener)
{
  bool removed = false;

  std::vector <eInput_iListener *>::iterator listener =
    listeners_.begin ();
  std::vector <eInput_iListener *>::iterator end =
    listeners_.end ();

  while (eSTL::IterValid (listener, end))
  {
    if ((*listener) == pListener) {
      listeners_.erase (listener);
      removed = true;
    }

    ++listener;
  }

  return removed;
}


bool
eInput::OnKeyPress (Epsilon::Keyboard::Key& key)
{
  /* Run through all installed listeners, notifying each of the
       event ... */
  std::vector <eInput_iListener *>::iterator listener =
    listeners_.begin ();
  std::vector <eInput_iListener *>::iterator end =
    listeners_.end ();

  while (eSTL::IterValid (listener, end)) {
    if ((*listener)->OnKeyPress (key))
      return true;

    ++listener;
  }

  return false;
}

bool
eInput::OnKeyRelease (Epsilon::Keyboard::Key& key)
{
  /* Run through all installed listeners, notifying each of the
       event ... */
  std::vector <eInput_iListener *>::iterator listener =
    listeners_.begin ();
  std::vector <eInput_iListener *>::iterator end =
    listeners_.end ();

  while (eSTL::IterValid (listener, end)) {
    if ((*listener)->OnKeyRelease (key))
      return true;

    ++listener;
  }

  return false;
}

/* eTB_Printf (...) */
#include "states.h"
#include "console.h"

bool
eInput::OnButtonPress (eMouse::Buttons::Button& button)
{
  dirty_state_ = true;

  /* If the mouse moved at the same time as the button was
       pressed, just ignore the delta x / delta y! */
  mouse_state_.MoveCursorDelta (0, 0, false);

  return true;
}

bool
eInput::OnButtonRelease (eMouse::Buttons::Button& button)
{
  dirty_state_ = true;

  /* If the mouse moved at the same time as the button was
       released, just ignore the delta x / delta y! */
  mouse_state_.MoveCursorDelta (0, 0, false);
  
  return true;
}

bool
eInput::OnMouseMove (int x, int y)
{
  dirty_state_  = true;
  cursor_pos_x_ = x;
  cursor_pos_y_ = y;

  return true;
}

/*
bool
eTB_Input::OnKeyPress (Epsilon::Keyboard::Key& key)
{
  return false;
}

bool
eTB_Input::OnKeyRelease (Epsilon::Keyboard::Key& key)
{
  return false;
}
*/

void
eInput::ShowCursor (bool bShow)
{
  /* ALWAYS use the rendered cursor, rather than the
       hardware cursor... */
#ifdef WIN32
  // As of 6/17/2011, the HW cursor is always hidden in Win32
  //   when the cursor is within the window's client area...
  ///////window_->ShowCursor (false);
#endif

  if (bShow) {
    show_cursor_ = true;
  } else {
    show_cursor_ = false;
  }
}
